home *** CD-ROM | disk | FTP | other *** search
- /* @(#)src/spool.c 1.8 7/11/92 11:51:10 */
-
- /*
- * Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
- * Copyright (C) 1992 Ronald S. Karr
- *
- * See the file COPYING, distributed with smail, for restriction
- * and warranty information.
- */
-
- /*
- * spool.c:
- * message spooling and retrieval. This source file implements a
- * reliable spooling system which is resiliant against inaccessible
- * directories, create errors and write errors. The algorithms here
- * are set up such that alternate directories can be used in the case
- * that smail is not able to complete spooling to a primary spool
- * directory.
- *
- * NOTE: This section will probably require substantial
- * modifications to work with a non-UN*X operating
- * system.
- *
- * external functions: creat_spool, write_spool, open_spool,
- * close_spool, unlink_spool, seek_spool,
- * tell_spool, send_spool, read_spool,
- * log_spool_errors, new_grade, defer_message,
- * message_date
- */
- #include "defs.h"
- #include <stdio.h>
- #include <ctype.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <errno.h>
- #include "smail.h"
- #include "spool.h"
- #include "transport.h"
- #include "log.h"
- #include "dys.h"
- #include "exitcodes.h"
- #ifndef DEPEND
- # include "debug.h"
- # include "extern.h"
- #endif
-
- #if defined(POSIX_OS) || !defined(UNIX_BSD)
- # include <time.h>
- #else /* not UNIX_BSD */
- # include <sys/time.h>
- #endif /* not UNIX_BSD */
-
- #if defined(UNIX_SYS5) || defined(POSIX_OS)
- # include <fcntl.h>
- #else
- # ifdef UNIX_BSD
- # include <sys/file.h>
- # endif
- #endif
-
- #ifdef STANDALONE
- # define xmalloc malloc
- # define xfree free
- extern char *malloc();
- int force_write_error = FALSE;
- #endif /* STANDALONE */
-
- /* variables exported from this file */
- char *message_id = NULL; /* unique ID for this message */
- char *spool_dir; /* directory used to spool message */
- char *spool_fn = NULL; /* basename of open spool file */
- char *input_spool_fn = NULL; /* name in input/ directory */
- int spoolfile = -1; /* open spool file */
- char *lock_fn; /* name of lock file for spool_fn */
- char *msg_buf; /* i/o buffer for spool file */
- char *msg_ptr; /* read placeholder in msg_buf */
- char *msg_max; /* last valid char in msg_buf */
- char *end_msg_buf; /* end of msg_buf */
- long msg_foffset; /* file offset for msg_buf contents */
- long msg_size; /* size of spool file */
-
- /* types local to this file */
- struct log_msgs {
- struct log_msgs *succ;
- char msg[1];
- };
- enum locker { l_creat, l_open, l_lock }; /* who is trying to lock */
-
- /* variables local to this file */
- static char *temp_fn = NULL; /* temp spool file name */
- static char *funct_name; /* name of current function */
- static char *old_spool_dir; /* saved value of spool_dir */
- static char *old_spool_fn; /* saved value of spool_fn */
- static char *old_input_spool_fn; /* saved value of input_spool_fn */
- static int old_spoolfile; /* saved value of spoolfile */
- static struct log_msgs *log_msgs; /* message lines to send to log */
- static struct log_msgs **next_log_msg; /* where to put next log message */
-
- /* functions local to this file */
- static int int_creat_spool();
- static void build_spool_fn();
- static void build_message_id();
- static int set_alt_spool();
- static int lock_spoolfile();
- static void copy_old_names();
- static void failed_write();
- static int copy_old_spool();
- static void log_message();
-
- /* functions imported from libc */
- long lseek();
- struct tm *localtime();
- struct tm *gmtime();
- char *ctime();
-
- /* trap undefined open flags */
- #ifndef O_RDONLY
- # define O_RDONLY 0
- #endif
- #ifndef O_WRONLY
- # define O_WRONLY 1
- #endif
- #ifndef O_RDWR
- # define O_RDWR 2
- #endif
-
-
- /*
- * creat_spool - create a spool file
- *
- * This is external entrypoint for spool file creation. It initializes some
- * state and then calls int_creat_spool() to do the real work.
- *
- * If spoolfile creation succeeded, return SUCCEED. Also, the following
- * external variables will be set:
- *
- * spool_dir - the directory in which the spoolfile was created
- * spool_fn - the basename of the generated file
- * input_spool_fn - the name of the spool file in the input directory
- * spoolfile - the file descriptor (used by the PUTSPOOL macro)
- * message_id - a unique message ID computed from the filename
- * msg_buf,msg_ptr - points to a region in which the message can be
- * written into memory
- *
- * In addition, the current directory will be set to spool_dir, and the file
- * will be locked against premature attempts at delivery by background
- * processes.
- *
- * If spoolfile creation failed, return FAIL. In this case, spool_fn,
- * input_spool_fn, spool_dir and message_id will all be set to NULL.
- */
- int
- creat_spool()
- {
- DEBUG(DBG_SPOOL_HI, "create_spool called\n");
- funct_name = "creat_spool";
- temp_fn = NULL;
- spool_fn = NULL;
- input_spool_fn = NULL;
- message_id = NULL;
- lock_fn = NULL;
- spoolfile = -1;
- log_msgs = NULL;
- next_log_msg = &log_msgs;
- msg_foffset = 0;
- msg_size = 0;
- if (msg_buf == NULL) {
- /* allocate the spool i/o buffer if it does not yet exist */
- msg_buf = xmalloc(message_bufsiz);
- }
- msg_max = msg_buf; /* start at beginning of buffer */
- msg_ptr = msg_buf;
- end_msg_buf = msg_buf + message_bufsiz;
- if (int_creat_spool() == FAIL) {
- DEBUG(DBG_SPOOL_LO, "creat_spool failed!\n");
- return FAIL;
- }
- DEBUG2(DBG_SPOOL_LO, "new spool file is %s/%s\n",
- spool_dir, input_spool_fn);
- return SUCCEED;
- }
-
- /*
- * int_creat_spool - create a spool file (internal form)
- *
- * try to create a spool file in either the primary or alternate spool
- * directory. The file will be locked and empty at the end of creat_spool.
- *
- * return SUCCEED or FAIL. If FAIL, then the spool file and lock file
- * will not exist unless they could not be unlinked.
- * log_spool_errors should be called once the log files are opened, as
- * well.
- */
- static int
- int_creat_spool()
- {
- int attempts = 0; /* # open attempts in a directory */
- char build_temp_fn[sizeof("input/msg.dddddddddd")];
-
- /* first try the primary spool directory */
- spool_dir = NULL;
-
- /* get the first "alternate" directory, which is the primary directory */
- if (set_alt_spool((char *)NULL) == FAIL) {
- return FAIL;
- }
-
- /*
- * create the temporary spool file in the first spool directory
- * that the open succeeds in.
- */
- (void) sprintf((temp_fn = build_temp_fn), "input/msg.%d", getpid());
- for (;;) {
- /*
- * unfortunately, the errno returned by creat(2) does not
- * distinguish between directory and file access errors, so
- * we use the open call, which does, for systems that
- * support it
- *
- * for systems with O_SYNC but without fsync() use O_SYNC to
- * ensure that the file is on disk before we return any responses
- * to the user.
- */
- #ifdef O_EXCL
- #if defined(O_SYNC) && !defined(HAVE_FSYNC) && !defined(NO_SPOOL_O_SYNC)
- spoolfile = open(temp_fn, O_RDWR|O_CREAT|O_EXCL|O_SYNC, spool_mode);
- #else
- spoolfile = open(temp_fn, O_RDWR|O_CREAT|O_EXCL, spool_mode);
- #endif /* O_SYNC && !HAVE_FSYNC && !NO_SPOOL_O_SYNC */
- #else /* O_EXCL */
- /*
- * if we must use creat, then the mode should not allow writing.
- * As well, we will have to reopen for reading later on in
- * read_spool, if we try to read on the file without closing
- * and then opening again.
- */
- spoolfile = creat(temp_fn, spool_mode&(~0222));
- if (spoolfile < 0 && errno == EACCES) {
- /* we assume that EACCES does not represent a directory error */
- errno = EEXIST;
- }
- #endif /* O_EXCL */
- if (spoolfile < 0 && errno == ENOENT) {
- /* NOENT means the directory did not exist, try to make it */
- DEBUG1(DBG_SPOOL_LO, "make directory %s/input\n", spool_dir);
- (void) mkdir("input", auto_mkdir_mode);
- #ifdef O_EXCL
- #if defined(O_SYNC) && !defined(HAVE_FSYNC) && !defined(NO_SPOOL_O_SYNC)
- spoolfile = open(temp_fn, O_RDWR|O_CREAT|O_EXCL|O_SYNC, spool_mode);
- #else
- spoolfile = open(temp_fn, O_RDWR|O_CREAT|O_EXCL, spool_mode);
- #endif /* O_SYNC && !HAVE_FSYNC && !NO_SPOOL_O_SYNC */
- #else
- spoolfile = creat(temp_fn, spool_mode&(~0222));
- if (spoolfile < 0 && errno == EACCES) {
- errno = EEXIST;
- }
- #endif
- }
- if (spoolfile < 0) {
- if (errno == EEXIST && attempts == 0) {
- /* if the spool file existed, then try to unlink it */
- DEBUG2(DBG_SPOOL_MID,
- "int_creat_spool: %s/input/%s exists, unlinking\n",
- spool_dir, temp_fn);
- attempts++; /* only attempt unlink once */
- continue;
- }
-
- /* otherwise we need to try an alternate spool directory */
- if (set_alt_spool("cannot create spool file") == FAIL) {
- return FAIL;
- }
- attempts = 0; /* new directory, reset try count */
- continue;
- }
-
- /*
- * we have an open temp file. Now create a lock file
- * and move the temp file to its more permanent name.
- */
- build_spool_fn(spool_grade);
- #ifndef lock_fd
- if (lock_spoolfile(l_creat) == FAIL) {
- /* we need to try an alternate directory */
- (void) close(spoolfile);
- (void) unlink(temp_fn);
- if (set_alt_spool("cannot lock message") == FAIL) {
- return FAIL;
- }
- continue;
- }
- #else /* lock_fd */
- if ((lock_by_name && lock_spoolfile(l_creat) == FAIL) ||
- (! lock_by_name && lock_fd(spoolfile) == FAIL))
- {
- (void) close(spoolfile);
- (void) unlink(temp_fn);
- if (set_alt_spool("cannot lock message") == FAIL) {
- return FAIL;
- }
- continue;
- }
- #endif /* lock_fd */
- if (rename(temp_fn, input_spool_fn) < 0) {
- /* why would rename fail? */
- if (lock_by_name) {
- (void) unlink(lock_fn);
- }
- (void) close(spoolfile);
- (void) unlink(temp_fn);
- if (set_alt_spool("failed to rename temp file") == FAIL) {
- return FAIL;
- }
- continue;
- }
-
- /* we have a valid locked spool file */
- return SUCCEED;
- }
- }
-
- /*
- * build_spool_fn - form the spool filename
- *
- * as a side effect, generate the lock filename, if needed, and
- * generate the message_id.
- *
- * the grade_char represents a priority which is appended to the
- * filename.
- */
- static void
- build_spool_fn(grade_char)
- int grade_char;
- {
- /* store the actual spool file basename here */
- static char fn[SPOOL_FN_LEN + 1];
- /* name of spool file in input directory */
- static char ifn[sizeof("input/") + SPOOL_FN_LEN];
- /* store the lock file name here */
- static char lfn[sizeof("lock/") + SPOOL_FN_LEN];
- extern long time();
- long clock = time((long *)0);
- struct stat statbuf;
- char a_inode[8]; /* ASCII base 62 inode number */
-
- /* get the inode number in base 62 information */
- (void) fstat(spoolfile, &statbuf);
- (void) strcpy(a_inode, base62((long)statbuf.st_ino));
-
- /* this is 14 chars which should work on all UN*X systems */
- spool_fn = fn;
- (void) sprintf(spool_fn, "%s-%s%c", base62(clock), a_inode, grade_char);
-
- /* use this to build the path in the input directory */
- input_spool_fn = ifn;
- (void) sprintf(input_spool_fn, "input/%s", spool_fn);
-
- /* have the message-ID computed from the computed spool file name */
- build_message_id();
- if (lock_by_name) {
- lock_fn = lfn;
- (void) sprintf(lock_fn, "lock/%s", spool_fn);
- }
- DEBUG2(DBG_SPOOL_HI, "build_spool_fn: try spool file %s/%s\n",
- spool_dir, input_spool_fn);
- }
-
- /*
- * build_message_id - build message_id from the value of spool_fn
- *
- * the only difference is that the message-Id begins with the letter `m', to
- * make it a valid local-addr vis-a-vis RFC822.
- */
- static void
- build_message_id()
- {
- static char m_id[sizeof("m") + SPOOL_FN_LEN];
-
- message_id = m_id;
- (void) sprintf(message_id, "m%s", spool_fn);
- }
-
-
- /*
- * lock_message - lock the current message
- *
- * When forking a process to deliver mail, locks are released by the
- * parent process and then regained in the child process by calling
- * lock_message(). If the lock cannot be regained then the child
- * process should exit, assuming that some other process has decided
- * to attempt delivery. This method is necessary when using lock
- * files, as pids are used to determine whether the locking process
- * still exists. It is also necessary when using the System V lockf
- * call as this does not preserve locks in a child of the process that
- * made the lock.
- *
- * Returns SUCCEED if the lock was obtained, FAIL otherwise.
- */
- int
- lock_message()
- {
- struct stat statbuf;
- int success;
- long offset;
-
- #ifdef lock_fd
- if (lock_by_name) {
- success = lock_spoolfile(l_lock);
- } else {
- offset = lseek(spoolfile, 0L, 1);
- (void) lseek(spoolfile, 0L, 0);
- success = lock_fd(spoolfile);
- (void) lseek(spoolfile, offset, 0);
- }
- #else
- success = lock_spoolfile(l_lock);
- #endif
- if (success == FAIL) {
- return FAIL;
- }
- (void) fstat(spoolfile, &statbuf);
-
- /*
- * If the file has been removed, then don't process it.
- */
- if (statbuf.st_nlink == 0) {
- DEBUG2(DBG_SPOOL_MID,
- "open_spool: %s/%s: spool file was removed\n",
- spool_dir, input_spool_fn);
- close_spool();
- return FAIL;
- }
- return SUCCEED;
- }
-
- /*
- * unlock_message - unlock the current message
- *
- * The parent process calls this before forking a process to perform
- * delivery. See lock_message() for an explanation.
- */
- void
- unlock_message()
- {
- long offset;
-
- #ifdef lock_fd
- if (lock_by_name) {
- #endif
- if (lock_fn) {
- DEBUG2(DBG_SPOOL_HI, "close_spool: unlinking %s/lock/%s\n",
- spool_dir, lock_fn);
- (void) unlink(lock_fn);
- }
- #ifdef lock_fd
- } else {
- offset = lseek(spoolfile, 0L, 1);
- (void) lseek(spoolfile, 0L, 0);
- unlock_fd(spoolfile);
- (void) lseek(spoolfile, offset, 0);
- }
- #endif
- }
-
- /*
- * lock_spoolfile - create a lock file for the spool file
- *
- * algorithm:
- * 1. create lock file exclusively. If this fails, goto step 3.
- * 2. write ASCII process id, followed by newline. If this succeeds,
- * then the lock succeeded, close the file and return.
- * 3. stat the lock file
- * 4. if st_size != 0, the proceed to step 10
- * 5. The lock file is empty because the locking process has
- * not yet written out its process id, or because the
- * process or system crashed prior to the write.
- * If the current time < Oct 28, 1986, then proceed to step 7.
- * If st_ctime > the current time then proceed to step 8.
- * If st_ctime < `now' - 2 hours proceed to step 9.
- * 6. The empty lock file is too new to touch (the system could
- * be very heavely loaded) and so we simply conclude that
- * some process has it locked and go on to do something else.
- * 7. Since this version of smail did not exist on Oct 28, 1986,
- * the current time must be set wrong. At this point
- * we give up all hope of stale lock file detection,
- * consider the file locked and go on to do something else.
- * 8. The empty lock file was created in the future so we don't
- * know if the empty lock file is stale or not. We first
- * bring the st_ctime of the file back into reality by
- * doing a chmod (to the same permission). This forces
- * the st_ctime to be set to `now'. Next we send the
- * following error message to the system log:
- *
- * time warp on zero length lock file: lock/0571338010a72y
- *
- * We consider the lock file active for now.
- * 9. The empty lock file is very likely stale. We will
- * remove the lock file but we will still consider it
- * locked, allowing some future process to operate
- * 10. The lock file contains the process id of the locking
- * process. We open the lock file and read it to find
- * the associated pid.
- * If pid == 0, then proceed to step 13.
- * If pid is not a process, then proceed to step 12.
- * 11. The process pid is a valid and currently
- * running process, and thus the lock file is really
- * valid. At this point we go on to do something else.
- * 12. The lock file is very likely stale. We will remove the
- * lock file but we will still consider it locked, allowing
- * some future process to operate on the spool file. We
- * now go on to do something else.
- * 13. If pid == 0, then the lock file is a
- * permanent lock file. (perhaps set by the system
- * administrator to freeze a mail message) At this
- * point we go on to do something else.
- *
- * return either SUCCEED or FAIL, and set errno to EEXIST if
- * the spoolfile is already locked.
- */
- static int lock_creat();
- static int check_empty_lock();
- static int verify_lock();
-
- static int
- lock_spoolfile(who)
- enum locker who; /* who is locking, open or creat */
- {
- struct stat statbuf; /* temp buf for stats */
- int attempts = 0;
- int mkdir_tried = FALSE; /* TRUE if mkdir("lock") tried */
-
- DEBUG1(DBG_SPOOL_HI, "lock_spoolfile called, lock_fn = %s\n", lock_fn);
- /*
- * loop until we have a lock or we fail to get a lock
- */
- for (;;) {
- /*
- * try to create one
- */
- if (lock_creat() == FAIL) {
- if (errno == ENOENT && !mkdir_tried && auto_mkdir) {
- mkdir_tried = TRUE;
- DEBUG1(DBG_SPOOL_LO, "make directory %s/lock\n",
- spool_dir);
- (void) mkdir("lock", auto_mkdir_mode);
- continue;
- }
- if (errno != EEXIST) {
- /* it failed, but not because it exists, can't handle this */
- DEBUG1(DBG_SPOOL_HI, "create failed: %s\n", strerrno());
- return FAIL;
- }
- if (who == l_creat) {
- /*
- * for creat_spool, no lock file should exist because
- * of inode uniqueness on a filesystem. Just unlink
- * the lock file and try again (once).
- */
- if (attempts == 0) {
- (void) unlink(lock_fn);
- attempts++;
- continue;
- }
- DEBUG3(DBG_SPOOL_MID,
- "lock_spoolfile: %s/lock/%s: lock failed: %s\n",
- spool_dir, lock_fn, strerrno());
- return FAIL;
- }
- } else {
- /* the create worked, great! */
- return SUCCEED;
- }
-
- if (stat(lock_fn, &statbuf) < 0) {
- /* failed to stat the file, hmmm */
- DEBUG3(DBG_SPOOL_LO,
- "lock_spoolfile: %s/lock/%s: stat failed: %s\n",
- spool_dir, lock_fn, strerrno());
- return FAIL;
- }
-
- if (statbuf.st_size == 0) {
- if (check_empty_lock(&statbuf) == SUCCEED) {
- errno = EEXIST; /* lock was upheld, for now */
- return FAIL;
- }
- } else {
- if (verify_lock() == SUCCEED) {
- errno = EEXIST; /* lock was upheld */
- return FAIL;
- }
- }
-
- /*
- * we consider the lock to be stale, so unlink it,
- * and let a future process try again.
- */
- (void) unlink(lock_fn);
- DEBUG2(DBG_SPOOL_LO,
- "lock_spoolfile: %s/lock/%s: stale lock file removed\n",
- spool_dir, lock_fn);
- errno = EEXIST;
- return FAIL;
- }
- }
-
- /*
- * lock_creat - try to create a lock and write into it the current pid
- *
- * this is only called from lock_spoolfile and returns SUCCEED or FAIL.
- */
- static int
- lock_creat()
- {
- int lfd;
- char apid[BITS_PER_INT/3 + 2]; /* should hold the process id */
- int ct;
-
- #ifdef O_EXCL
- lfd = open(lock_fn, O_RDWR|O_CREAT|O_EXCL, lock_mode);
- #else /* O_EXCL */
- /*
- * it is time for the silly creat trick again
- */
- lfd = creat(lock_fn, lock_mode&(~0222));
- if (lfd < 0 && errno == EACCES) {
- errno = EEXIST;
- }
- #endif /* O_EXCL */
- if (lfd < 0) {
- /* we failed to creat the lock the file */
- return FAIL;
- }
- (void) sprintf(apid, "%u\n", getpid());
- ct = strlen(apid);
- if (write(lfd, apid, ct) < ct) {
- /* we failed to write the pid into the lock file, quit */
- (void) unlink(lock_fn);
- (void) close(lfd);
- return FAIL;
- }
- (void) close(lfd);
- return SUCCEED;
- }
-
- /*
- * check_empty_lock - decide if zero-length lock file is stale
- *
- * return SUCCEED if not stale and should be kept, FAIL otherwise.
- */
- static int
- check_empty_lock(stp)
- struct stat *stp; /* statbuf from lock_spoolfile */
- {
- struct tm *ctm;
- extern long time();
- long now = time((long *)0); /* verify based on age */
-
- ctm = gmtime(&now);
- if (ctm->tm_year <= 86 && ctm->tm_mon <= 10 && ctm->tm_mday < 28) {
- /*
- * it is before chongo's birthday, but on a year that must be
- * in the past. Since this version of smail has not been
- * written yet, punt.
- */
- write_log(LOG_SYS|LOG_CONS, "machine is in a time warp, check date");
- return SUCCEED;
- }
-
- if (stp->st_ctime > now) {
- /*
- * file created in the future, bring it back to the present, but
- * otherwise assume it is a valid lock.
- */
- (void) chmod(lock_fn, stp->st_mode);
- write_log(LOG_SYS, "time warp on zero length lock file: %s", lock_fn);
- return SUCCEED;
- }
- if (stp->st_ctime < now - 2*3600/*two hours in seconds*/) {
- /* lock not upheld, file can be removed */
- return FAIL;
- }
-
- /* a new lock file, leave it alone for now */
- DEBUG(DBG_SPOOL_HI, "new zero-length lock file, let it stand\n");
- return SUCCEED;
- }
-
- /*
- * verify_lock - decide if non-zero lockfile is stale
- *
- * return SUCCEED if not stale and should be kept, FAIL otherwise.
- */
- static int
- verify_lock()
- {
- int lfd;
- char rpid[BITS_PER_INT/3 + 2];
- int pid = 0;
- int ct;
-
- lfd = open(lock_fn, O_RDONLY);
- if (lfd < 0) {
- /* failed to open the lock file, don't consider it stale yet */
- DEBUG3(DBG_SPOOL_HI, "verify_lock: %s/lock/%s: %s\n",
- spool_dir, lock_fn, strerrno());
- return SUCCEED;
- }
-
- /* read in the pid */
- ct = read(lfd, rpid, sizeof(rpid)-1);
- if (ct <= 0) {
- /* failed to read anything from the lock file, ignore it for now */
- (void) close(lfd);
- DEBUG3(DBG_SPOOL_HI, "verify_lock: %s/lock/%s: read faile: %s\n",
- spool_dir, lock_fn, strerrno());
- return SUCCEED;
- }
- (void) close(lfd);
-
- rpid[ct] = '\0'; /* firewall */
- (void) sscanf(rpid, "%d", &pid);
- if (pid == 0) {
- /* pid is 0 or not a valid number, consider the lock valid */
- DEBUG(DBG_SPOOL_HI, "verify_lock: pid==0 lock considered valid\n");
- return SUCCEED;
- }
-
- /*
- * does the process exist? before you look at the man page,
- * kill(pid,0) detects process existence on all versions of
- * UN*X I know of including v6 and v7
- */
- if (kill(pid, 0) < 0 && errno == ESRCH) {
- /* process does not exist, lock is stale */
- return FAIL;
- }
-
- /* the lock is valid */
- DEBUG2(DBG_SPOOL_HI, "verify_lock: %s/lock/%s: valid lock\n",
- spool_dir, lock_fn);
- return SUCCEED;
- }
-
-
- /*
- * write_spool - write completed block to the spool file
- *
- * write the contents of the spool i/o buffer out to the spool file,
- * at the current i/o position. But otherwise don't change anything.
- *
- * If the write fails then try to create an alternate spool file and
- * copy into it the contents previously written to the old spool file.
- *
- * NOTE: writing to the spool file should be done through the macro
- * PUTSPOOL(c) defined in spool.h, except that the final write
- * should be done with a call to write_spool. PUTSPOOL will
- * manage the various associated pointers.
- */
- int
- write_spool()
- {
- old_spool_fn = NULL; /* set to old name if new created */
- old_input_spool_fn = NULL;
- old_spool_dir = NULL;
- old_spoolfile = spoolfile;
-
- funct_name = "write_spool";
- DEBUG(DBG_SPOOL_HI, "write_spool called\n");
-
- /*
- * loop until we have successfully written the block to something,
- * or until we cannot recover from past errors
- */
- for (;;) {
- if (write(spoolfile, msg_buf, msg_max - msg_buf) == msg_max - msg_buf
- #ifdef STANDALONE
- /* it is a bit tough to get random write errors while testing */
- && (!force_write_error || (force_write_error = FALSE))
- #endif /* STANDALONE */
- )
- {
- break;
- } else {
- /*
- * write failed, unlink the old spool file and copy contents
- * to a new spool file.
- */
- copy_old_names();
- failed_write();
- for (;;) {
- /* get an alternate spool directory */
- if (set_alt_spool("write to spool failed") == FAIL) {
- (void) close(old_spoolfile);
- DEBUG(DBG_SPOOL_LO,
- "write_spool: no more spool dirs, write failed\n");
- return FAIL;
- }
-
- if (int_creat_spool() == FAIL) {
- (void) close(old_spoolfile);
- DEBUG(DBG_SPOOL_LO,
- "write_spool: can't create new spool file, write failed\n");
- return FAIL;
- }
-
- if (msg_size == 0) {
- break;
- } else {
- int code = copy_old_spool();
-
- if (code == READ_FAIL) {
- DEBUG(DBG_SPOOL_LO,
- "write_spool: failed to copy old spool file: read failed\n");
- return FAIL;
- } else if (code == SUCCEED) {
- break;
- }
- DEBUG(DBG_SPOOL_LO,
- "write_spool: failed to copy old spool file: write failed\n");
- }
- }
- }
- }
- if (old_spool_fn) {
- /* we succeeded in copying to a new spool file, don't need these */
- xfree(old_spool_fn);
- xfree(old_input_spool_fn);
- xfree(old_spool_dir);
- }
- msg_size += msg_max - msg_buf;
-
- return SUCCEED;
- }
-
- /*
- * copy_old_names - make a copy of the old spool file names
- */
- static void
- copy_old_names()
- {
- if (old_spool_fn == NULL) {
- /* save a copy of the old names if needed later */
- old_spool_fn = COPY_STRING(spool_fn);
- old_input_spool_fn = COPY_STRING(input_spool_fn);
- old_spool_dir = COPY_STRING(spool_dir);
- }
- }
-
- /*
- * failed_write - remove old files after a write error
- *
- * NOTE: we aren't closing them, so the data in them can still be read.
- */
- static void
- failed_write()
- {
- if (spool_fn) {
- (void) unlink(input_spool_fn);
- spool_fn = NULL;
- input_spool_fn = NULL;
- }
- if (lock_fn) {
- (void) unlink(lock_fn);
- }
- }
-
- /*
- * copy_old_spool - copy old spool file to the new one
- *
- * return READ_FAIL on read error, SUCCEED on success, and
- * WRITE_FAIL on write error
- */
- static int
- copy_old_spool()
- {
- char *tempbuf = xmalloc(message_bufsiz);
- int copied = 0; /* count of chars copied to new file */
-
- /*
- * previous contents exist, we will need to copy them
- */
- (void) lseek(old_spoolfile, 0L, 0);
- while (copied < msg_size) {
- register int ct;
-
- /* fill up as much of the temp buffer as reasaonble */
- if (msg_size - copied < message_bufsiz) {
- ct = msg_size - copied;
- } else {
- ct = message_bufsiz;
- }
-
- ct = read(old_spoolfile, tempbuf, ct);
- if (ct <= 0) {
- failed_write();
- log_message("read failed in copying to new file");
- (void) close(spoolfile);
- (void) close(old_spoolfile);
- return READ_FAIL; /* read error - can't recover */
- } else {
- if (write(spoolfile, tempbuf, ct) < ct) {
- failed_write();
- return WRITE_FAIL; /* write error - maybe can recover */
- }
- }
- copied += ct;
- }
-
- return SUCCEED; /* we have recovered */
- }
-
-
- /*
- * open_spool - open and possibly lock the given spool file
- *
- * the filename is assumed to be absolute, so a chdir is done to
- * the directory name, and spool_fn is set to the basename. This
- * process is destructive of the spool file string passed.
- *
- * As a side effect, msg_buf will be loaded with the first part of the
- * spool file.
- *
- * return SUCCEED or FAIL. If fail, load error with error message.
- * errno will be 0 if no system error was involved.
- */
- int
- open_spool(fn, lock, error)
- char *fn; /* basename of spool file */
- int lock; /* if TRUE, lock the file */
- char **error; /* return error message here */
- {
- static char lfn[sizeof("lock/") + SPOOL_FN_LEN];
- struct stat statbuf;
-
- DEBUG1(DBG_SPOOL_HI, "open_spool(%s) called\n", fn);
- funct_name = "open_spool";
- message_id = NULL;
- lock_fn = NULL;
- spoolfile = -1;
- log_msgs = NULL;
- msg_foffset = 0;
- msg_size = 0;
- errno = 0;
- if (msg_buf == NULL) {
- /* allocate the spool i/o buffer if it does not yet exist */
- msg_buf = xmalloc(message_bufsiz);
- }
- msg_max = msg_buf; /* start at beginning of buffer */
- end_msg_buf = msg_buf + message_bufsiz;
- if (fn[0] != '/') {
- DEBUG1(DBG_SPOOL_LO, "open_spool: %s: relative pathname!\n", fn);
- *error = "filename not absolute";
- return FAIL;
- }
- spool_dir = fn; /* get directory from fn */
- spool_fn = rindex(fn, '/'); /* get basename from fn */
- input_spool_fn = spool_fn - sizeof("/input") + 1;
- spool_fn++;
- if (input_spool_fn < spool_dir ||
- strncmp(input_spool_fn, "/input", sizeof("/input") - 1))
- {
- DEBUG1(DBG_SPOOL_LO, "open_spool failed: %s: invalid spool filename",
- spool_dir);
- *error = "invalid spool filename";
- return FAIL;
- }
- *input_spool_fn++ = '\0';
-
- if (chdir(spool_dir) < 0) {
- DEBUG2(DBG_SPOOL_LO, "open_spool failed: %s: chdir failed: %s",
- spool_dir, strerrno());
- *error = "chdir failed";
- return FAIL;
- }
-
- /*
- * attempt to open the file
- *
- * some of the OS locking algorithms require that the file be
- * opened for writing. For such systems, the spool_mode must have
- * at least ownership write permission.
- */
- #ifdef LOCK_REQUIRES_WRITE
- if (lock && ! lock_by_name) {
- spoolfile = open(input_spool_fn, O_RDWR);
- } else
- #endif
- {
- spoolfile = open(input_spool_fn, O_RDONLY);
- }
- if (spoolfile < 0) {
- DEBUG3(DBG_SPOOL_LO, "open_spool: %s/%s: open failed: %s\n",
- spool_dir, input_spool_fn, strerrno());
- *error = "open failed";
- return FAIL;
- }
-
- if (lock) {
- #ifndef lock_fd
- /* only lock_by_name is possible */
- (void) sprintf(lfn, "lock/%s", spool_fn);
- lock_fn = lfn;
- if (lock_spoolfile(l_open) == FAIL) {
- DEBUG2(DBG_SPOOL_MID, "open_spool: %s/%s: lock failed\n",
- spool_dir, input_spool_fn);
- (void) close(spoolfile);
- *error = "lock failed";
- return FAIL;
- }
- #else /* lock_fd */
- if (lock_by_name) {
- (void) sprintf(lfn, "lock/%s", spool_fn);
- lock_fn = lfn;
- if (lock_spoolfile(l_open) == FAIL) {
- DEBUG2(DBG_SPOOL_MID, "open_spool: %s/%s: lock failed\n",
- spool_dir, input_spool_fn);
- (void) close(spoolfile);
- *error = "lock failed";
- return FAIL;
- }
- } else {
- if (lock_fd(spoolfile) == FAIL) {
- DEBUG2(DBG_SPOOL_MID, "open_spool: %s/%s: lock failed\n",
- spool_dir, input_spool_fn);
- (void) close(spoolfile);
- *error = "lock failed";
- return FAIL;
- }
- }
- #endif /* lock_fd */
- }
-
- /*
- * Get statistics on spool file.
- */
- (void) fstat(spoolfile, &statbuf);
-
- /*
- * If the file has been removed, then don't process it.
- */
- if (statbuf.st_nlink == 0) {
- DEBUG2(DBG_SPOOL_MID,
- "open_spool: %s/%s: spool file was removed\n",
- spool_dir, input_spool_fn);
- close_spool();
- return FAIL;
- }
-
- /*
- * now that the file itself is locked, we need to setup the
- * message_id and initialize the msg_buf pointers so that
- * read_spool will be called the first time the GETSPOOL
- * macro is called.
- */
- build_message_id();
- if (msg_buf == NULL) {
- msg_buf = xmalloc(message_bufsiz);
- end_msg_buf = msg_buf + message_bufsiz;
- }
- msg_max = msg_buf;
- msg_ptr = msg_buf;
- (void) fstat(spoolfile, &statbuf);
- msg_size = statbuf.st_size;
-
- if (lock) {
- DEBUG2(DBG_SPOOL_LO, "opened and locked spool file %s/%s\n",
- spool_dir, input_spool_fn);
- } else {
- DEBUG2(DBG_SPOOL_LO, "opened spool file %s/%s\n",
- spool_dir, input_spool_fn);
- }
- return SUCCEED;
- }
-
-
- /*
- * close_spool - unlock and close the spool file
- */
- void
- close_spool()
- {
- DEBUG(DBG_SPOOL_HI, "close_spool called\n");
- if (lock_fn) {
- DEBUG2(DBG_SPOOL_HI, "close_spool: unlinking %s/lock/%s\n",
- spool_dir, lock_fn);
- (void) unlink(lock_fn);
- }
- (void) close(spoolfile);
- spoolfile = -1;
- spool_fn = NULL;
- input_spool_fn = NULL;
- }
-
- /*
- * unlink_spool - unlock, close and unlink the spool file
- */
- void
- unlink_spool()
- {
- char *save_input_spool_fn = input_spool_fn;
-
- DEBUG(DBG_SPOOL_HI, "unlink_spool called\n");
- if (save_input_spool_fn) {
- DEBUG2(DBG_SPOOL_HI, "unlink_spool: unlinking: %s/%s\n",
- spool_dir, save_input_spool_fn);
- (void) unlink(save_input_spool_fn);
- }
- close_spool();
- }
-
- /*
- * defer_message - put the spool file in a defer directory
- *
- * some processing errors may need attention from the postmaster,
- * and may be correctable through a change in configuration, or other
- * such things. Rather than mail such problems to the sender or to
- * the postmaster, put them in a special (error/) directory. When
- * the problem that caused the error is resolved, the postmaster can
- * then simply move the spool file back into the spool directory and
- * delivery will be reattempted.
- */
- void
- defer_message()
- {
- char error_fn[sizeof("error/") + SPOOL_FN_LEN];
- int success;
- char *save_input_spool_fn = input_spool_fn;
- char *save_spool_fn = spool_fn;
-
- (void) sprintf(error_fn, "error/%s", spool_fn);
- spool_fn = NULL;
- success = link(save_input_spool_fn, error_fn);
- if (success < 0 && auto_mkdir && errno == ENOENT) {
- DEBUG1(DBG_SPOOL_LO, "make directory %s/error\n", spool_dir);
- (void) mkdir("error", auto_mkdir_mode);
- success = link(save_input_spool_fn, error_fn);
- }
- if (success < 0) {
- write_log(LOG_PANIC|LOG_SYS,
- "defer_message: error linking %s/%s to error/%s: %s",
- spool_dir, save_input_spool_fn, spool_fn, strerrno());
- } else {
- if (errfile) {
- write_log(LOG_TTY|LOG_SYS, "mail moved to %s/error/%s",
- spool_dir, save_spool_fn);
- }
- input_spool_fn = save_input_spool_fn;
- spool_fn = save_spool_fn;
- unlink_spool();
- }
- }
-
-
- /*
- * seek_spool - seek to the specified absolute spool file offset
- *
- * causes the aligned block around that point to be read into
- * msg_buf.
- */
- int
- seek_spool(offset)
- register long offset;
- {
- DEBUG1(DBG_SPOOL_HI, "seek_spool(%ld) called\n", offset);
- if (offset > msg_size) {
- /* attempt to seek past the end of message */
- msg_ptr = msg_max + 1; /* will cause EOF from GETSPOOL */
- DEBUG(DBG_SPOOL_LO, "seek_spool failed, seek past end of file\n");
- return FAIL;
- }
-
- /* is the offset in the current block? */
- if (msg_foffset <= offset && offset < msg_foffset + (msg_max - msg_buf)) {
- /* yes, just adjust the current loc pointer */
- msg_ptr = msg_buf + (offset - msg_foffset);
- return SUCCEED;
- }
-
- /* compute the beginning of the block containing the offset */
- msg_foffset = offset - offset%message_bufsiz;
-
- /* actually read in the message */
- (void) lseek(spoolfile, msg_foffset, 0);
- /* set the buffer to zero-length so read_spool won't advance foffset */
- msg_max = msg_buf;
- if (read_spool() == FAIL) {
- return FAIL;
- }
-
- /* set up the ptr to point to the byte for the offset */
- msg_ptr = msg_buf + (offset - msg_foffset);
- if (msg_ptr > msg_max) {
- DEBUG(DBG_SPOOL_LO, "seek_spool failed, read came up short\n");
- return FAIL;
- }
- return SUCCEED;
- }
-
- /*
- * tell_spool - return the current offset in the spool file.
- */
- int
- tell_spool()
- {
- return msg_foffset + msg_ptr - msg_buf;
- }
-
-
- /*
- * send_spool - write the spool file to a stdio FILE pointer
- *
- * send the spool file to an open file, starting at the current
- * offset and ending at the end of the spool file. If PUT_DOTS is set
- * then use the hidden dot algorithm, prepending a dot to each
- * line that begins with a dot. If PUT_CRLF is set than put a carriage
- * return before each newline. If UNIX_FROM_HACK then put > in front of
- * any line beginning with From.
- *
- * return SUCCEED, READ_FAIL or WRITE_FAIL.
- */
- int
- send_spool(f, flags)
- register FILE *f; /* file to write on */
- long flags; /* transport flags */
- {
- register char *p;
- int eof = FALSE; /* TRUE if found end of file */
- register int last_c = '\n';
- int dot = flags & PUT_DOTS;
- int crlf = flags & PUT_CRLF;
- int uucp_hack = flags & UNIX_FROM_HACK;
-
- DEBUG(DBG_SPOOL_HI, "send_spool called\n");
- while (!eof) {
- for (p = msg_ptr; p < msg_max; p++) {
- if (last_c == '\n') {
- if (dot && *p == '.') {
- putc('.', f); /* hidden dot algorithm */
- }
- if (uucp_hack && *p == 'F') {
- int ct = msg_max - p;
-
- /* look ahead to see if this line begins with From */
- if (ct > sizeof("From ") - 1) {
- if (strncmp("From ", p, sizeof("From ") - 1) == 0) {
- putc('>', f);
- }
- } else if (strncmp("From ", p, ct) == 0) {
- /*
- * we have to look ahead to the next block to
- * determine if this is a From, but make sure
- * there is another block, first.
- */
- if (msg_foffset + (msg_max - msg_buf) < msg_size) {
- int i;
-
- msg_ptr = p;
- if (read_spool() == FAIL) {
- return READ_FAIL;
- }
- p = msg_ptr;
- if (strncmp("From " + ct, p,
- sizeof("From ") - ct - 1) == 0)
- {
- putc('>', f);
- }
- for (i = 0; i < ct; i++) {
- putc("From "[i], f);
- }
- }
- }
- }
- }
- if (*p == '\n' && crlf) {
- putc('\r', f); /* ARPAnet CR/LF */
- }
- if (putc(last_c = *p, f) == EOF) {
- DEBUG(DBG_SPOOL_LO, "send_spool: write failed\n");
- return WRITE_FAIL;
- }
- }
- msg_ptr = p;
- if (msg_foffset + (msg_max - msg_buf) < msg_size) {
- if (read_spool() == FAIL) {
- return READ_FAIL;
- }
- } else {
- eof = TRUE;
- }
- }
-
- if (last_c != '\n') {
- if (crlf) {
- putc('\r', f);
- }
- if (putc('\n', f) == EOF) {
- DEBUG(DBG_SPOOL_LO, "send_spool: write failed\n");
- return WRITE_FAIL;
- }
- }
-
- return SUCCEED;
- }
-
-
- /*
- * read_spool - read in a block from the spool file
- *
- * read a block starting at the current file position into msg_buf
- * and update the various associated variables.
- *
- * return SUCCEED or FAIL.
- */
- int
- read_spool()
- {
- register int ct;
-
- /* advance the current file offset by the amount previously read */
- msg_foffset += msg_max - msg_buf;
- if (message_bufsiz > msg_size - msg_foffset) {
- /* the remaining size is less then a whole buffer */
- ct = msg_size - msg_foffset;
- if (ct < 0) {
- errno = ENXIO; /* reading beyond end of the file */
- DEBUG(DBG_SPOOL_LO, "read_spool: read failed, past end of file");
- return FAIL;
- }
- if (ct == 0) {
- /* end of file */
- msg_max = msg_ptr = msg_buf;
- DEBUG(DBG_SPOOL_MID, "end of file on spool file\n");
- return FAIL;
- }
- } else {
- /* there is enough data to fit in a complete buffer */
- ct = message_bufsiz;
- }
-
- /* now, where were we? */
- (void) lseek(spoolfile, msg_foffset, 0);
-
- /* read in a buffer */
- ct = read(spoolfile, msg_buf, ct);
- if (ct < 0) {
- DEBUG1(DBG_SPOOL_LO, "read_spool: read of spool file failed: %s\n",
- strerrno());
- return FAIL;
- }
-
- /* point to the end and beginning of the used region */
- msg_max = msg_buf + ct;
- msg_ptr = msg_buf;
-
- return SUCCEED;
- }
-
-
- /*
- * log_spool_errors - write spooling errors to system log
- *
- * while spooling, errors are not written out to the spool files. This
- * avoid having to deal with the fact that those writes may fail. Thus,
- * when finished spooling the message, we must write out any errors to the
- * panic file.
- */
- void
- log_spool_errors()
- {
- struct log_msgs *next;
-
- DEBUG(DBG_SPOOL_HI, "log_spool_errors called, saving errors from spool\n");
- while (log_msgs) {
- next = log_msgs->succ;
- write_log(LOG_PANIC, "%s", log_msgs->msg);
- xfree((char *)log_msgs);
- log_msgs = next;
- }
- }
-
- /*
- * log_message - save a message to be written to the log file
- *
- * the message is not in printf format, but filename and directory
- * information will be added to it.
- */
- static void
- log_message(m)
- char *m; /* message */
- {
- char *p, *q; /* temp */
- register unsigned a; /* how much to alloc */
- char *save;
-
- a = sizeof(*log_msgs) + strlen(m);
- if (spool_dir) {
- a += sizeof(", dir=") + strlen(spool_dir);
- }
- if (input_spool_fn) {
- a += sizeof(", spoolfile=") + strlen(input_spool_fn);
- }
- if (lock_fn) {
- a += sizeof(", lockfile=") + strlen(lock_fn);
- }
- *next_log_msg = (struct log_msgs*)xmalloc(a);
- (*next_log_msg)->succ = NULL;
- save = p = (*next_log_msg)->msg;
- next_log_msg = &(*next_log_msg)->succ;
- /* copy message */
- for (q = m; *q; *p++ = *q++) ;
- if (spool_dir) {
- /* put spool directory in message */
- for (q = ", dir="; *q; *p++ = *q++) ;
- for (q = spool_dir; *q; *p++ = *q++) ;
- }
- if (input_spool_fn) {
- /* put spoolfile name in message */
- for (q = ", spoolfile="; *q; *p++ = *q++) ;
- for (q = input_spool_fn; *q; *p++ = *q++) ;
- }
- if (lock_fn) {
- /* put lockfile name in message */
- for (q = ", lockfile="; *q; *p++ = *q++) ;
- for (q = lock_fn; *q; *p++ = *q++) ;
- }
- *p++ = '\0';
- DEBUG2(DBG_SPOOL_MID, "%s: %s\n", funct_name, save);
- }
-
-
- /*
- * set_alt_spool - set a primary or alternate spool directory
- *
- * step through the list of spool directories, advancing once per
- * call to set_alt_spool and resetting when the current spool directory
- * was not set by set_alt_spool. Do a chdir(2) to the directory
- * set spool_dir and return SUCCEED on the first one for chdir
- * succeeds. If none remain, return FAIL.
- */
- static int
- set_alt_spool(m)
- char *m; /* message to put in log */
- {
- static char altname[SIZE_FILNAM];
- static char *altlist = NULL;
- char *p;
-
- /* log the message using the old spool dir then set the new one */
- if (m) {
- log_message(m);
- }
-
- if (spool_dir != altname) {
- /* previous spool dir not one of ours, reset to start */
- altlist = spool_dirs;
- }
- spool_dir = altname;
-
- /* loop until we have a valid alternate spool directory, or none left */
- while (altlist && *altlist) {
- /* copy a directory name into altname, may end in ':' or nul */
- for (p = altname; *altlist && *altlist != ':'; *p++ = *altlist++) ;
- if (*altlist == ':') {
- /* next try gets the next directory */
- altlist++;
- }
- *p++ = '\0'; /* terminate directory name */
-
- if (altname[0] != '/') {
- /* directory must begin with '/', ignore it */
- log_message("config error: alt directory doesn't begin with /");
- continue;
- }
-
- if (chdir(altname) < 0) {
- /* Okay, try to make the directory and then chdir again */
- if (auto_mkdir && errno == ENOENT) {
- DEBUG1(DBG_SPOOL_LO, "make directory %s\n", altname);
- (void) mkdir(altname, auto_mkdir_mode);
- if (chdir(altname) < 0) {
- log_message("chdir failed");
- continue;
- }
- } else {
- log_message("chdir failed");
- continue;
- }
- }
-
- /* we found a valid alternate directory */
- return SUCCEED;
- }
-
- /* we failed, *sigh* */
- return FAIL;
- }
-
- /*
- * new_grade - assign a new grade to the spool file
- *
- * assign a new grade, or precedence, code to the spool file. This
- * involves linking to a new name with a different precedence character.
- * We go through build_spool_fn to compute new names and keep trying
- * new names until we have one that doesn't already exist. If links
- * fail for some reason other than a name not existing then don't bother
- * trying to change the grade.
- *
- * return SUCCEED or FAIL, though a FAIL should be ignored as there is
- * no good way to recover and it is not generally that important.
- */
- int
- new_grade(grade)
- int grade; /* grade character */
- {
- /*
- * NOTE: the above must be the same size as the names
- * built by build_spool_fn
- */
- char save_spool_fn[SPOOL_FN_LEN + 1];
- char save_input_spool_fn[sizeof("input/") + SPOOL_FN_LEN + 1];
- char save_message_id[SPOOL_FN_LEN + 2];
- char save_lock_fn[sizeof("lock/") + SPOOL_FN_LEN + 1];
-
- DEBUG1(DBG_SPOOL_HI, "new_grade(%c) called\n", grade);
- /*
- * make a copy of the old names to use as args to link, and so that
- * the old names can be restored
- */
- (void) memcpy(save_spool_fn, spool_fn, sizeof(save_spool_fn));
- (void) memcpy(save_input_spool_fn, input_spool_fn,
- sizeof(save_input_spool_fn));
- (void) memcpy(save_message_id, message_id, sizeof(save_message_id));
- if (lock_by_name) {
- (void) memcpy(save_lock_fn, lock_fn, sizeof(save_lock_fn));
- }
-
- /*
- * loop until we link both lock file and spool file to new names
- * and unlink the old ones.
- */
- for (;;) {
- build_spool_fn(grade);
-
- /* try to link the lock file first so that it is initially locked */
- if (lock_by_name && link(save_lock_fn, lock_fn) < 0) {
- if (errno != EEXIST) {
- DEBUG1(DBG_SPOOL_LO, "new_grade: failed to link lock: %s\n",
- strerrno());
- break;
- }
- DEBUG1(DBG_SPOOL_MID,
- "new_grade: failed to link lock:%, try another\n",
- strerrno());
- continue;
- }
-
- /* now try to link the actual spool file */
- if (link(save_input_spool_fn, input_spool_fn) < 0) {
- if (errno != EEXIST) {
- DEBUG1(DBG_SPOOL_LO, "new_grade: failed to link spool: %s\n",
- strerrno());
- if (lock_by_name) {
- (void) unlink(lock_fn);
- }
- break;
- }
- DEBUG1(DBG_SPOOL_MID,
- "new_grade: failed to link spool file: %s, try another\n",
- strerrno());
- continue;
- }
-
- /* success, now unlink the old names */
- (void) unlink(save_input_spool_fn);
- if (lock_by_name) {
- (void) unlink(save_lock_fn);
- }
- return SUCCEED;
- }
-
- /* we failed, copy the old names back */
- (void) memcpy(spool_fn, save_spool_fn, sizeof(save_spool_fn));
- (void) memcpy(input_spool_fn, save_input_spool_fn,
- sizeof(save_input_spool_fn));
- (void) memcpy(message_id, save_message_id, sizeof(save_message_id));
- if (lock_by_name) {
- (void) memcpy(lock_fn, save_lock_fn, sizeof(save_lock_fn));
- }
-
- return FAIL;
- }
-
- /*
- * message_date - return the date that the message was spooled
- *
- * The message spool date is encoded in the actual Message Id as a
- * base 62 number. Return the date as the number of seconds since
- * the epoch.
- */
- long
- message_date()
- {
- long clock;
- register int digit;
- register char *p;
-
- clock = 0;
- for (p = message_id + 1; *p; p++) {
- if (isdigit(*p)) {
- digit = *p - '0';
- } else if (isupper(*p)) {
- digit = *p - ('A' - 10);
- } else if (islower(*p)) {
- digit = *p - ('a' - 36);
- } else {
- break;
- }
- clock = (clock * 62) + digit;
- }
- return clock;
- }
-
-
- #ifdef STANDALONE
-
- #include "varargs.h"
-
- char *program = "spool";
- char *spool_dirs = "/usr/spool/smail:/wd1h/spool/smail:/usr/tmp/spool/smail";
- int debug = 0;
- int message_bufsiz = MESSAGE_BUF_SIZE;
- int spool_mode = 0444;
- int spool_grade = 'C';
- int lock_mode = 0444;
-
- /*
- * standalone main for spooling subsystem.
- * usage:
- * spool [-ddebug] [-msize] o|c[w][u] [filename]
- *
- * -d sets the debug level, -m sets message_bufsiz. An arg of `o'
- * causes a spool file in the given filename to be locked, written to
- * standard out and closed. An arg of `c' causes a spool file to be
- * read from standard input and put in a new spool file. If `w'
- * follows `o' or `c' then write the spool file to standard out before
- * closing. If `u' then unspool the message.
- */
- void
- main(argc, argv)
- int argc;
- char *argv[];
- {
- char *p;
- int mode = 0;
- int dowrite = FALSE;
- int dounspool = FALSE;
-
- program = *argv++;
- --argc;
-
- while ((*argv)[0] == '-') {
- switch ((*argv)[1]) {
- case 'd':
- debug = atoi(*argv + 2);
- break;
- case 'm':
- message_bufsiz = atoi(*argv + 2);
- break;
- default:
- usage();
- /*NOTREACHED*/
- }
- argv++;
- --argc;
- }
-
- if (argc <= 0) {
- usage();
- /*NOTREACHED*/
- }
- for (p = *argv++, --argc; *p; p++) {
- switch (*p) {
- case 'o':
- if (mode) {
- usage();
- /*NOTREACHED*/
- }
- mode = 'o';
- break;
- case 'c':
- if (mode) {
- usage();
- /*NOTREACHED*/
- }
- mode = 'c';
- break;
- case 'w':
- dowrite = TRUE;
- break;
- case 'u':
- dounspool = TRUE;
- break;
- default:
- usage();
- /*NOTREACHED*/
- }
- }
- if (!mode) {
- usage();
- /*NOTREACHED*/
- }
- if (mode == 'o' && argc <= 0) {
- (void) fprintf(stderr, "%s: open mode requires filename\n", program);
- exit(EX_USAGE);
- }
- if (mode == 'o') {
- if (open_spool(*argv) < 0) {
- (void) fprintf(stderr, "%s: open failed\n", program);
- exit(EX_OSFILE);
- }
- } else {
- register int c;
-
- if (creat_spool(*argv) < 0) {
- (void) fprintf(stderr, "%s: creat failed\n", program);
- exit(EX_OSFILE);
- }
- while ((c = getchar()) != EOF) {
- if (c == '@') {
- force_write_error = TRUE;
- }
- if (PUTSPOOL(c) == FAIL) {
- (void) fprintf(stderr, "%s: write failed\n", program);
- exit(EX_IOERR);
- }
- }
- if (write_spool() == FAIL) {
- (void) fprintf(stderr, "%s: write failed\n", program);
- }
- log_spool_errors();
- }
-
- if (dowrite) {
- if (seek_spool(0L) != SUCCEED) {
- (void) fprintf(stderr, "seek_spool failed\n");
- }
- if (send_spool(stdout, TRUE, FALSE) != SUCCEED) {
- (void) fprintf(stderr, "seek_spool failed\n");
- }
- }
- if (dounspool) {
- unlink_spool();
- } else {
- close_spool();
- }
- exit(EX_OK);
- }
-
- usage()
- {
- (void) fprintf(stderr, "usage: %s [-ddebug] [-msize] o|c[w][u] [file]\n",
- program);
- exit(EX_USAGE);
- }
-
- /*VARARGS2*/
- void
- write_log(log, fmt, va_alist)
- int log; /* TRUE if to write global log file */
- char *fmt; /* printf(3) format */
- va_dcl /* arguments for printf */
- {
- va_list ap;
-
- va_start(ap);
- (void)fprintf(stderr, log? "PUBLIC: ": "PRIVATE: ");
- (void)vfprintf(stderr, fmt, ap);
- putc('\n', stderr);
- va_end(ap);
- }
-
- char *
- strerrno()
- {
- char errmsg[100];
-
- (void) sprintf(errmsg, "Error_%d", errno);
- return errmsg;
- }
-
- #endif /* STANDALONE */
-